﻿using System;
using System.Diagnostics;
using RaytracerLib.MathLib;
using TomanuExtensions;
using RaytracerLib.Rays;

namespace RaytracerLib.MathLib
{
    public struct Plane
    {
        public readonly Vector3 Normal;
        public readonly double Dist;
        public readonly Vector3 Pos;

        public Plane(Vector3 a_normal, double a_dist)
        {
            Debug.Assert(a_normal.IsNormalized);

            Normal = a_normal;
            Dist = a_dist;
            Pos = Normal * Dist;
        }

        public Plane(Vector3 a_normal, Vector3 a_v)
        {
            Debug.Assert(a_normal.IsNormalized);

            Normal = a_normal;
            Dist = Normal * a_v;
            Pos = Normal * Dist;
        }

        public Plane(Vector3 a_v1, Vector3 a_v2, Vector3 a_v3)
        {
            Normal = Vector3.CrossProduct(a_v2 - a_v1, a_v3 - a_v1).Normalized;

            Debug.Assert(Normal.IsNormalized);

            Dist = Normal * a_v1;
            Pos = Normal * Dist;
        }

        public Vector3 GetIntersection(Ray a_ray)
        {
            double dist = GetDist(a_ray);

            if (dist == Double.PositiveInfinity)
                return Vector3.POSITIVE_INFINITY;

            return a_ray.Start + dist * a_ray.Dir;
        }

        public Vector3 GetIntersection(Vector3 a_ray_start, Vector3 a_ray_dir)
        {
            Debug.Assert(a_ray_dir.IsNormalized);

            double dist = GetDist(a_ray_start, a_ray_dir);

            if (dist == Double.PositiveInfinity)
                return Vector3.POSITIVE_INFINITY;

            return a_ray_start + dist * a_ray_dir;
        }

        public static double GetDist(Vector3 m_normal, Vector3 a_plane_vertex,
            Vector3 a_point)
        {
            Debug.Assert(m_normal.IsNormalized);

            return m_normal * (a_point - a_plane_vertex);
        }

        public double GetDist(Vector3 a_point)
        {
            return Normal * a_point - Dist;
        }

        public double GetDist(Ray a_ray)
        {
            return GetDist(a_ray.Start, a_ray.Dir);
        }

        public double GetDist(Vector3 a_ray_start, Vector3 a_ray_dir)
        {
            Debug.Assert(a_ray_dir.IsNormalized);

            double ca = -(a_ray_dir * Normal);

            if (ca.IsAlmostRelativeEquals(0))
                return Double.PositiveInfinity;

            return (Normal * a_ray_start - Dist) / ca;
        }

        public static double GetDist(Vector3 a_ray_start, Vector3 a_ray_dir, 
            Vector3 a_plane_normal, Vector3 a_plane_vertex)
        {
            Debug.Assert(a_ray_dir.IsNormalized);

            double ca = -(a_ray_dir * a_plane_normal);

            if (ca.IsAlmostRelativeEquals(0))
                return Double.PositiveInfinity;

            double dist = -(a_plane_normal * a_plane_vertex);

            return (a_plane_normal * a_ray_start - dist) / ca;
        }
    }
}